package br.cin.ufpe.corba.namingservice;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Set;

import br.cin.ufpe.corba.ORB;
import br.cin.ufpe.corba.ObjectRef;
import br.cin.ufpe.corba.namingservice.NotFound.NotFoundReason;
import br.cin.ufpe.corba.namingservice.binding.Binding;
import br.cin.ufpe.corba.namingservice.binding.BindingList;
import br.cin.ufpe.corba.namingservice.binding.BindingType;

public class NamingContextImpl extends NamingContextPOA implements Serializable {

	private static final long serialVersionUID = -6668042069162915549L;
	
	private HashMap<Binding, ObjectRef> mapping = new HashMap<Binding, ObjectRef>();

	public NamingContextImpl(ORB orb) {
		super(orb);
	}
	
	public void bind(Name name, Object obj) throws AlreadyBound {
		if (!name.isCompoundName()) {
			Binding binding = new Binding(name.getHead(), BindingType.OBJECT);
			if (!mapping.containsKey(binding)) {
				mapping.put(binding, (ObjectRef)obj);
			} else {
				throw new AlreadyBound();
			}
		} else {
			Binding binding = new Binding(name.getHead(), BindingType.CONTEXT);
			if (!mapping.containsKey(binding)) {
				mapping.put(binding, (ObjectRef)new_context());
			}

			NamingContextImpl child = (NamingContextImpl) mapping.get(binding);
			child.bind(name.getTail(), obj);
		}
	}

	public void bind_context(Name name, NamingContext nc) {
		Binding binding = new Binding(name.getHead(), BindingType.CONTEXT);

		if (!name.isCompoundName()) {
			if (!mapping.containsKey(binding)) {
				mapping.put(binding, (ObjectRef)new_context());
			} else {
				throw new AlreadyBound();
			}
		} else {
			if (!mapping.containsKey(binding)) {
				mapping.put(binding, (ObjectRef)new_context());
			}
			NamingContextImpl child = (NamingContextImpl) mapping.get(binding);
			
			child.bind_context(name.getTail(), nc);
		}
	}

	public NamingContext bind_new_context(Name name) {
		Binding binding = new Binding(name.getHead(), BindingType.CONTEXT);

		if (!name.isCompoundName()) {
			if (!mapping.containsKey(binding)) {
				mapping.put(binding, (ObjectRef)new_context());
			} else {
				throw new AlreadyBound();
			}
			return (NamingContext) mapping.get(binding);
		} else {
			if (!mapping.containsKey(binding)) {
				mapping.put(binding, (ObjectRef)new_context());
			}
			NamingContextImpl child = (NamingContextImpl) mapping.get(binding);			
			return child.bind_new_context(name.getTail());
		}
	}

	public void destroy() throws NotEmpty {
		if (this.mapping.size() > 0) {
			throw new NotEmpty();
		}
	}

	public NamingContext new_context() {
		NamingContextImpl nc = new NamingContextImpl(get_orb());
		orb.add(nc);		 
		return nc;
	}

	public void rebind(Name name, Object obj) throws NotFound {
		boolean throwException = false;
		
		try {
			Object result = resolve(name);			
			// If already bound the previous binding must be of type "nobject"
			if (result instanceof NamingContextImpl) {
				throwException = true;
			} else {			
				unbind(name);
			}
		} catch (NotFound e) {}
		
		if (throwException) {
			throw new NotFound(name, NotFoundReason.not_object);
		}
		
		bind(name, obj);
	}

	public void rebind_context(Name name, NamingContext nc) {
		boolean throwException = false;
		
		try {
			Object result = resolve(name);			
			// If already bound the previous binding must be of type "ncontext"
			if (result instanceof RemoteObjectData) {
				throwException = true;
			} else {			
				unbind(name);
			}
		} catch (NotFound e) {}
		
		if (throwException) {
			throw new NotFound(name, NotFoundReason.not_context);
		}
		bind_context(name, nc);
	}

	public ObjectRef resolve(Name name) throws NotFound {
		if (!name.isCompoundName()) {
			Binding binding = new Binding(name.getHead(), BindingType.OBJECT);
			if (mapping.containsKey(binding)) {
				return mapping.get(binding);
			} else {
				throw new NotFound(name, NotFoundReason.missing_node);
			}
		} else {
			Binding binding = new Binding(name.getHead(), BindingType.CONTEXT);
			if (!mapping.containsKey(binding)) {
				throw new NotFound(name, NotFoundReason.missing_node);
			}

			NamingContextImpl childContext = (NamingContextImpl) mapping.get(binding);
			return childContext.resolve(name.getTail());
		}
	}

	public ObjectRef resolve_str(String name) throws NotFound, InvalidName {
		return resolve(to_name(name));
	}
	
	public void unbind(Name name) {		
		if (!name.isCompoundName()) {
			Binding binding = new Binding(name.getHead(), BindingType.OBJECT);
			if (mapping.containsKey(binding)) {
				mapping.remove(binding);
			} 
		} else {
			Binding binding = new Binding(name.getHead(), BindingType.CONTEXT);
			
			NamingContextImpl childContext = (NamingContextImpl) mapping.get(binding);
			childContext.unbind(name.getTail());
			
			if (mapping.containsKey(binding)) {
				mapping.remove(binding);
			}
		}
	}

	public Name to_name(String sname) throws InvalidName {
		return new Name(sname);
	}

	public String to_string(Name name) throws InvalidName {
		if (name != null) {
			return name.toString();
		}
		return null;
	}

	public BindingList list(int how_many) {
		Set<Binding> keys = this.mapping.keySet();
		return new BindingList(keys, how_many);
	}

}
